package com.shanxia.codec;

import com.shanxia.codec.cmd.*;
import com.shanxia.codec.enums.DecodeEnum;

import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Locale;

import static java.util.Arrays.copyOfRange;

public class Decoder {

    public static byte[] hexStringToByteArray(String hexString) {
        int len = hexString.length() / 2;
        byte[] res = new byte[len];

        for (int i = 0; i < len; i++) {
            // 两位一个字节
            res[i] = (byte) ((Character.digit(hexString.charAt(2 * i), 16) << 4)
                    + Character.digit(hexString.charAt(2 * i + 1), 16));
        }
        return res;
    }

    /**
     * byte[]数组转换为16进制的字符串
     *
     * @param data 要转换的字节数组
     * @return 转换后的结果
     */
    public static String byteToHexString(byte[] data) {
        StringBuilder sb = new StringBuilder(data.length * 2);
        for (byte b : data) {
            int v = b & 0xff;
            if (v < 16) {
                sb.append('0');
            }
            sb.append(Integer.toHexString(v));
        }
        return sb.toString().toUpperCase(Locale.getDefault());
    }

    // BCD码
    public static String getBCDHexString(byte[] data) {
        byte[] temp = new byte[data.length * 2];
        for (int i = 0; i < data.length; i++) {
            temp[2 * i + 0] = (byte) ((data[i] >> 4) & 0x0F);
            temp[2 * i + 1] = (byte) ((data[i]) & 0x0F);
        }
        for (int i = 0; i < temp.length; i++) {
            if (temp[i] >= 10)
                temp[i] = (byte) ((byte) 'A' + temp[i] - 10);
            else
                temp[i] = (byte) ((byte) '0' + temp[i]);
        }
        return new String(temp);
    }

    /**
     * @功能: 10进制串转为BCD码
     * @参数: 10进制串
     * @结果: BCD码
     */
    public static byte[] str2Bcd(String asc) {
        int len = asc.length();
        int mod = len % 2;
        if (mod != 0) {
            asc = "0" + asc;
            len = asc.length();
        }
        byte abt[] = new byte[len];
        if (len >= 2) {
            len = len / 2;
        }
        byte bbt[] = new byte[len];
        abt = asc.getBytes();
        int j, k;
        for (int p = 0; p < asc.length() / 2; p++) {
            if ((abt[2 * p] >= '0') && (abt[2 * p] <= '9')) {
                j = abt[2 * p] - '0';
            } else if ((abt[2 * p] >= 'a') && (abt[2 * p] <= 'z')) {
                j = abt[2 * p] - 'a' + 0x0a;
            } else {
                j = abt[2 * p] - 'A' + 0x0a;
            }
            if ((abt[2 * p + 1] >= '0') && (abt[2 * p + 1] <= '9')) {
                k = abt[2 * p + 1] - '0';
            } else if ((abt[2 * p + 1] >= 'a') && (abt[2 * p + 1] <= 'z')) {
                k = abt[2 * p + 1] - 'a' + 0x0a;
            } else {
                k = abt[2 * p + 1] - 'A' + 0x0a;
            }
            int a = (j << 4) + k;
            byte b = (byte) a;
            bbt[p] = b;
        }
        return bbt;
    }

    /**
     * 字节数组转（ASCII对应的字符）
     *
     * @param byteArray byte[]
     * @return String
     */
    public static String byteToAscii(byte[] byteArray, boolean zeroReturn) {
        String result = "";
        char temp;

        int length = byteArray.length;
        for (int i = 0; i < length; i++) {
            // 如果开启了 遇到字节为 0x00 直接返回 则 执行以下判断
            if (zeroReturn && byteArray[i] == 0x00) {
                return result;
            }
            temp = (char) byteArray[i];
            result += temp;
        }
        return result;
    }

    public static void encodeAscii(byte[] buf, String asciiData, int len, int destPos) {
        byte[] tBytes = new byte[len];
        try {
            byte[] temp = asciiData.getBytes("US-ASCII");

            System.arraycopy(temp, 0, tBytes, 0, temp.length);

            if (temp.length < len) {
                for (int i = temp.length; i < len; i++) {
                    tBytes[i] = 0x00;
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        // 在原数组中设置
        System.arraycopy(tBytes, 0, buf, destPos, len);
    }

    /**
     * 对标准时间进行编码
     * @param data 数据
     * @param destArr 目标数组
     * @param destPos 目标位置
     * @param endBlank 第八位是否需要占位
     */
    public static void encodeBasicTime(String data, byte[] destArr, int destPos, boolean endBlank) {
        LocalDateTime time = LocalDateTime.parse(data, DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
        // 8字节时间戳，年月日时分秒
        int fyear = time.getYear() / 100;
        int lyear = time.getYear() % 100;
        int month = time.getMonth().getValue();
        int day = time.getDayOfMonth();
        int hour = time.getHour();
        int min = time.getMinute();
        int second = time.getSecond();
        String bcd = "" + fyear + lyear + month + day + hour + min + second;
        byte[] temp = Decoder.str2Bcd(bcd);
        System.arraycopy(temp, 0, destArr, destPos, temp.length);
        if (endBlank) {
            destArr[destPos + 7] = (byte) 0xFF;
        }
    }

    /**
     * 获得无符号8位整型
     * 通常目的：只保留低八位，把有可能的负数变正数
     *
     * @param b
     * @return
     */
    public static int getUint8(byte b) {
        return b & 0xFF;
    }

    /**
     * 从buf的start位置开始取两个字节，数据解析位无符号16型，存储位int
     * 低位在前
     *
     * @param buf
     * @param start
     * @return
     */
    public static int getUint16(byte[] buf, int start) {
        int temp = 0;
        // |=  就是 a = a | b
        temp |= buf[start + 1] & 0xFF;
        temp <<= 8;
        temp |= buf[start] & 0xFF;
        return temp;
    }

    /**
     * 第8位bit表示加密标志
     * 前4位bit表示主协议版本号
     * @param sign
     * @return
     */
    public static byte getInfoByte(int sign, int mainProtocolVersion) {
        int temp = 0;
        temp |= sign & 0x0F;
        temp <<= 7;
        temp |= mainProtocolVersion & 0x0F;
        return (byte)temp;
    }
    public static int getInt32(byte[] data, int offset) {
        return ByteBuffer.wrap(data, offset, 4).order(ByteOrder.LITTLE_ENDIAN).getInt();
    }

    /**
     * 将数据按照uint16解析得到2个字节填充到buf从start开始的位置
     *
     * @param buf
     * @param start
     * @param value
     */
    public static void getBytesAsUint16(byte[] buf, int start, int value) {
        buf[start] = (byte) (value & 0xFF);
        buf[start + 1] = (byte) ((value >> 8) & 0xFF);
    }

    /**
     * 小端模式将byte[]转成int
     */
    public static int bytesToIntLittle(byte[] buf, int start) {
        int value;
        value = (buf[start] & 0xFF)
                | ((buf[start + 1] & 0xFF) << 8)
                | ((buf[start + 2] & 0xFF) << 16)
                | ((buf[start + 3] & 0xFF) << 24);
        return value;
    }

    /**
     * 小端模式将int转成byte[]
     *
     * @param value
     * @return
     */
    public static byte[] intToBytesLittle(byte[] buf, int index, int value) {

        buf[index] = (byte) (value & 0xFF);
        buf[index + 1] = (byte) ((value >> 8) & 0xFF);
        buf[index + 2] = (byte) ((value >> 16) & 0xFF);
        buf[index + 3] = (byte) ((value >> 24) & 0xFF);
        return buf;
    }

    /**
     * 获取某个字节中，某几bit的值
     *
     * @param value  byte值
     * @param start  起始bit
     * @param length 长度
     */
    public static int getBits(byte value, int start, int length) {
        return (value >> start) & (0xFF >> (8 - length));
    }

    /**
     * 获得标准时钟时间
     *
     * @param dataField 数据字节数组
     * @param offset    起始偏移量
     * @return yyyy-MM-dd HH:mm:ss
     */
    public static String getStandardTime(byte[] dataField, int offset) {
        String yPart1 = getBCDHexString(copyOfRange(dataField, offset, offset+1));
        offset += 1;
        String yPart2 = getBCDHexString(copyOfRange(dataField, offset, offset+1));
        offset += 1;
        if (yPart1 == null || "".equals(yPart1) || yPart2 == null || "".equals(yPart2)) {
            return "";
        }
        int year = Integer.valueOf(yPart1 + yPart2);
        int month = Integer.valueOf(getBCDHexString(copyOfRange(dataField, offset, offset+1)));
        offset += 1;
        int day = Integer.valueOf(getBCDHexString(copyOfRange(dataField, offset, offset+1)));
        offset += 1;
        int hour = Integer.valueOf(getBCDHexString(copyOfRange(dataField, offset, offset+1)));
        offset += 1;
        int min = Integer.valueOf(getBCDHexString(copyOfRange(dataField, offset, offset+1)));
        offset += 1;
        int second = Integer.valueOf(getBCDHexString(copyOfRange(dataField, offset, offset+1)));
        offset += 1;
        return LocalDateTime
                .of(year, month, day, hour, min, second)
                .format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
    }

    public static ProtocolDataBase decodeData(byte[] data, int length) {

        ProtocolDataBase res = ProtocolDataBase.decodeData(data, length);

        if(res.getResult() != DecodeEnum.Success)
            return res;
        // TODO 待实现相应的 命令 解码
        switch(res.getCmd()) {
            //) 充 电 桩 参 数 整 形 设 置/ / 查 询 应 答
            case 2:
                return Cmd2.decodeFromBase(res);
            //) 充 电 桩 参 数 字 符 形 设 置/ / 查 询 应 答
            case 4:
                return Cmd4.decodeFromBase(res);
            //) 充 电 桩 对 后 台 控 制 命 令 应 答
            case 6:
                return Cmd6.decodeFromBase(res);
            //  充 电 桩 对 后 台 下 发 的 充 电 桩 开 启 充 电 控 制 应 答
            case 8:
                return Cmd8.decodeFromBase(res);
            //) 充 电 桩 上 传 命 令 请 求 (预 留)
            case 10:
                return Cmd10.decodeFromBase(res);

            //) 充 电 桩 上 传 心 跳 包 信息
            case 102:
                return Cmd102.decodeFromBase(res);
            //) 充 电 桩 状 态 信 息 包 上 传
            case 104:
                return Cmd104.decodeFromBase(res);
            //) 充 电 桩 签 到 信 息 上 传
            case 106:
                return Cmd106.decodeFromBase(res);
            //) 充 电 桩 告 警 信 息 上 报 (预 留)
            case 108:
                return Cmd108.decodeFromBase(res);
            //) 充 电 桩 上 报 充 电 启 动 完 成 命 令
            case 110:
                return Cmd110.decodeFromBase(res);
            //) 充 电 桩 模 块 信 息 上 报 (预 留)
            case 112:
                return Cmd112.decodeFromBase(res);
            //) 充 电 桩 应 答 服 务 器 查 询 最 近 一 次 充 电 各 时 段 信 息
            case 114:
                return Cmd114.decodeFromBase(res);


            //) 充 电 桩 上 报 充 电 记 录 信 息
            case 202:
                return Cmd202.decodeFromBase(res);
            // 充 电 桩 充 电 上 传 用 户 帐 户 查 询 报 文
            case 204:
                return Cmd204.decodeFromBase(res);
            //) 充 电 桩 上 传 用 户 密 码 验 证 报 文 (预 留)
            case 206:
                return Cmd206.decodeFromBase(res);
            //  充 电 桩 上 报 “ 即 插 即 充 “ 启 动 充 电 请 求
            case 208:
                return Cmd208.decodeFromBase(res);
            //) 充 电 桩 上 报 “ 即 插 即 充” ”  启 动 充 电 结 果
            case 210:
                return Cmd210.decodeFromBase(res);


            //) 充 电 桩 上 报 B BM MS S  信 息 (预 留)
            case 302:
                return Cmd302.decodeFromBase(res);
            //) 充 电 桩 上 报 B BM MS S  信 息 (预 留)
            case 304:
                return Cmd304.decodeFromBase(res);


            // 充 电 桩 上 报 历 史 的 充 电 记 录
            case 402:
                return Cmd402.decodeFromBase(res);


            //) 充 电 桩 回 复 服 务 器 下 发 擦 除 指 令
            case 1002:
                return Cmd1002.decodeFromBase(res);
            //  充 电 桩 应 答 服 务 器 下 发 升 级 文 件 名 指 令
            case 1004:
                return Cmd1004.decodeFromBase(res);
            //) 充 电 桩 应 答 服 务 器 下 发 升 级 文 件 大 小 指 令
            case 1006:
                return Cmd1006.decodeFromBase(res);
            //) 充 电 桩 应 答 服 务 器 下 发 升 级 文 件 数 据 指 令
            case 1008:
                return Cmd1008.decodeFromBase(res);
            // 充 电 桩 应 答 服 务 器 下 发 升 级 文 件 数 据 结 束 指 令
            case 1010:
                return Cmd1010.decodeFromBase(res);
            //) 充 电 桩 应 答 服 务 器 下 发 重 启 指 令
            case 1012:
                return Cmd1012.decodeFromBase(res);


            // 充 电 桩 应 答 后 台 服 务 器 查 询 24 时 电 费 计 价 策 略 信 息
            case 1102:
                return Cmd1102.decodeFromBase(res);
            // 充 电 桩 应 答 后 台 服 务 器 设 置 24 时 电 费 计 价 策 略 信 息
            case 1104:
                return Cmd1104.decodeFromBase(res);
            // 充 电 桩 回 复 2 24 4  时 电 费 计 价 策 略 信 息
            case 1108:
                return Cmd1108.decodeFromBase(res);


            //) 充 电 桩 密 码 登 入 报 文
            case 1202:
                return Cmd1202.decodeFromBase(res);


            //  充 电 桩 应 答 白 名 单 操 作 日 志 数 据
            case 1302:
                return Cmd1302.decodeFromBase(res);


            //) 充 电 桩 应 答 服 务 器 下 发 F FT TP P  远 程 升 级 命 令
            case 1402:
                return Cmd1402.decodeFromBase(res);
            //) 充 电 桩 应 答 F FT TP P  升 级 包 下 载 进 度
            case 1403:
                return Cmd1403.decodeFromBase(res);
            //) ) 充 电 桩 应 答 服 务 器 下 发 获 取 充 电 桩 日 志 命 令
            case 1407:
                return Cmd1407.decodeFromBase(res);
            default:
                res.setResult(DecodeEnum.NotImplement);
                return res;
        }
    }
}
